home *** CD-ROM | disk | FTP | other *** search
/ Aminet 48 / Aminet 48 (2002)(GTI - Schatztruhe)[!][Apr 2002].iso / Aminet / text / edit / vim60rt.lha / Vim / vim60 / macros / matchit.vim < prev    next >
Encoding:
Text File  |  2001-09-19  |  31.2 KB  |  861 lines

  1. "  matchit.vim: (global plugin) Extended "%" matching
  2. "  Last Change: August 28, 2001
  3. "  Maintainer:  Benji Fisher PhD   <benji@member.AMS.org>
  4. "  Version:     1.1
  5.  
  6. " Documentation:
  7. "  The documentation is in a separate file, matchit.txt .
  8.  
  9. " Credits:
  10. "  Vim editor   by Bram Moolenaar (Thanks, Bram!)
  11. "  Original script and design by Raul Segura Acevedo
  12. "  Support for comments by Douglas Potts
  13. "  Support for back references and other improvements by Benji Fisher
  14. "  Support for many languages by Johannes Zellner
  15. "  Suggestions for improvement, bug reports, and support for additional
  16. "  languages by Jordi-Albert Batalla, Neil Bird, Mark Collett, Stephen Wall,
  17. "  and Johannes Zellner.
  18.  
  19. " Debugging:
  20. "  If you'd like to try the built-in debugging commands...
  21. "   :MatchDebug      to activate debugging for the current buffer
  22. "  This saves the values of several key script variables as buffer-local
  23. "  variables.  See the MatchDebug() function, below, for details.
  24.  
  25. " TODO:  I should think about multi-line patterns for b:match_words.
  26. " TODO:  Maybe I should add a menu so that people will actually use some of
  27. " the features that I have implemented.
  28.  
  29. " allow user to prevent loading
  30. " and prevent duplicate loading
  31. if exists("loaded_matchit") || &cp
  32.   finish
  33. endif
  34. let loaded_matchit = 1
  35. let s:last_mps = ""
  36. let s:last_words = ""
  37.  
  38. let s:save_cpo = &cpo
  39. set cpo&vim
  40.  
  41. nnoremap <silent> %  :<C-U>call <SID>Match_wrapper('',1,'n') <CR>
  42. nnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'n') <CR>
  43. vnoremap <silent> %  :<C-U>call <SID>Match_wrapper('',1,'v') <CR>m'gv``
  44. vnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'v') <CR>m'gv``
  45. onoremap <silent> %  :<C-U>call <SID>Match_wrapper('',1,'o') <CR>
  46. onoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'o') <CR>
  47.  
  48. " Analogues of [{ and ]} using matching patterns:
  49. nnoremap <silent> [% :<C-U>call <SID>MultiMatch("bW", "n") <CR>
  50. nnoremap <silent> ]% :<C-U>call <SID>MultiMatch("W",  "n") <CR>
  51. vnoremap [% <Esc>[%m'gv``
  52. vnoremap ]% <Esc>]%m'gv``
  53. onoremap <silent> [% :<C-U>call <SID>MultiMatch("bW", "o") <CR>
  54. onoremap <silent> ]% :<C-U>call <SID>MultiMatch("W",  "o") <CR>
  55.  
  56. " Auto-complete mappings:  (not yet "ready for prime time")
  57. " TODO Read :help write-plugin for the "right" way to let the user
  58. " specify a key binding.
  59. "   let g:match_auto = '<C-]>'
  60. "   let g:match_autoCR = '<C-CR>'
  61. " if exists("g:match_auto")
  62. "   execute "inoremap " . g:match_auto . ' x<Esc>"=<SID>Autocomplete()<CR>Pls'
  63. " endif
  64. " if exists("g:match_autoCR")
  65. "   execute "inoremap " . g:match_autoCR . ' <CR><C-R>=<SID>Autocomplete()<CR>'
  66. " endif
  67. " if exists("g:match_gthhoh")
  68. "   execute "inoremap " . g:match_gthhoh . ' <C-O>:call <SID>Gthhoh()<CR>'
  69. " endif " gthhoh = "Get the heck out of here!"
  70.  
  71. " Highlight group for error messages.  The user may choose to set this
  72. " to be invisible.
  73. hi link MatchError WarningMsg
  74.  
  75. let s:notslash = '\\\@<!\%(\\\\\)*'
  76.  
  77. function! s:Match_wrapper(word, forward, mode) range
  78.   " If this function was called from Visual mode, make sure that the cursor
  79.   " is at the correct end of the Visual range:
  80.   if a:mode == "v"
  81.     execute "normal! gv\<Esc>"
  82.   endif
  83.   " Use default behavior if called with a count or if no patterns are defined.
  84.   if v:count
  85.     exe "normal! " . v:count . "%"
  86.     return
  87.   elseif !exists("b:match_words") || b:match_words == ""
  88.     silent! normal! %
  89.     return
  90.   end
  91.  
  92.   " First step:  if not already done, set the script variables
  93.   "   s:do_BR    flag for whether there are backrefs
  94.   "   s:pat    parsed version of b:match_words
  95.   "   s:all    regexp based on s:pat and the default groups
  96. " Thanks to Preben "Peppe" Guldberg and Bram Moolenaar for this suggestion!
  97.   if (b:match_words != s:last_words) || (&mps != s:last_mps) ||
  98.     \ exists("b:match_debug")
  99.     let s:last_words = b:match_words
  100.     let s:last_mps = &mps
  101.     if b:match_words !~ s:notslash . '\\\d'
  102.       let s:do_BR = 0
  103.       let s:pat = b:match_words
  104.     else
  105.       let s:do_BR = 1
  106.       let s:pat = s:ParseWords(b:match_words)
  107.     endif
  108.     " The next several lines were here before
  109.     " BF started messing with this script.
  110.     " quote the special chars in 'matchpairs', replace [,:] with \| and then
  111.     " append the builtin pairs (/*, */, #if, #ifdef, #else, #elif, #endif)
  112.     " let default = substitute(escape(&mps, '[$^.*~\\/?]'), '[,:]\+',
  113.     "  \ '\\|', 'g').'\|\/\*\|\*\/\|#if\>\|#ifdef\>\|#else\>\|#elif\>\|#endif\>'
  114.     let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") .
  115.       \ '\/\*:\*\/,#if\%(def\)\=:#else\>:#elif\>:#endif\>'
  116.     " s:all = pattern with all the keywords
  117.     let s:all = s:pat . (strlen(s:pat) ? "," : "") . default
  118.     let s:all = substitute(s:all, s:notslash . '\zs[,:]\+', '\\|', 'g')
  119.     let s:all = '\%(' . s:all . '\)'
  120.     " let s:all = '\%(' . substitute(s:all, '\\\ze[,:]', '', 'g') . '\)'
  121.     if exists("b:match_debug")
  122.       let b:match_pat = s:pat
  123.     endif
  124.   endif
  125.  
  126.   " Second step:  set the following local variables:
  127.   "     matchline = line on which the cursor started
  128.   "     curcol    = number of characters before match
  129.   "     prefix    = regexp for start of line to start of match
  130.   "     suffix    = regexp for end of match to end of line
  131.   " Require match to end on or after the cursor and prefer it to
  132.   " start on or before the cursor.
  133.   let matchline = getline(".")
  134.   let startcol = col(".")
  135.   if a:word != ''
  136.     " word given
  137.     if a:word !~ s:all
  138.       echohl MatchError|echo 'Missing rule for word:"'.a:word.'"'|echohl NONE
  139.       return
  140.     endif
  141.     let matchline = a:word
  142.     let curcol = 0
  143.     let prefix = '^\%('
  144.     let suffix = '\)$'
  145.   " Now the case when "word" is not given
  146.   elseif matchend(matchline, '.*' . s:all) < startcol
  147.     " there is no special word in this line || it ends before the cursor
  148.     echohl MatchError | echo "No matching rule applies here" | echohl NONE
  149.     return
  150.   else    " Find the match that ends on or after the cursor and set curcol.
  151.     let regexp = s:Wholematch(matchline, s:all, startcol-1)
  152.     let curcol = match(matchline, regexp)
  153.     let suf = strlen(matchline) - matchend(matchline, regexp)
  154.     let prefix = (curcol ? '^.\{'  . curcol . '}\%(' : '^\%(')
  155.     let suffix = (suf ? '\).\{' . suf . '}$'  : '\)$')
  156.     " If the match comes from the defaults, bail out.
  157.     if matchline !~ prefix .
  158.       \ substitute(s:pat, s:notslash.'\zs[,:]\+', '\\|', 'g') . suffix
  159.       norm! %
  160.       return
  161.     endif
  162.   endif
  163.   if exists("b:match_debug")
  164.     let b:match_match = matchstr(matchline, regexp)
  165.     let b:match_col = curcol+1
  166.   endif
  167.  
  168.   " Third step:  Find the group and single word that match, and the original
  169.   " (backref) versions of these.  Then, resolve the backrefs.
  170.   " Set the following local variables:
  171.   " group = colon-separated list of patterns, one of which matches
  172.   "       = ini:mid:fin or ini:fin
  173.   "
  174.   " Reconstruct the version with unresolved backrefs.
  175.   let patBR = substitute(b:match_words.',',
  176.     \ s:notslash.'\zs[,:]*,[,:]*', ',', 'g')
  177.   let patBR = substitute(patBR, s:notslash.'\zs:\{2,}', ':', 'g')
  178.   " Now, set group and groupBR to the matching group: 'if:endif' or
  179.   " 'while:endwhile' or whatever.
  180.   let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR)
  181.   let i = matchend(group, s:notslash . ",")
  182.   let groupBR = strpart(group, i)
  183.   let group = strpart(group, 0, i-1)
  184.   " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix
  185.   if s:do_BR " Do the hard part:  resolve those backrefs!
  186.     let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline)
  187.   endif
  188.  
  189.   " Fourth step:  Set the arguments for searchpair().
  190.   let i = matchend(group, s:notslash . ":")
  191.   let j = matchend(group, '.*' . s:notslash . ":")
  192.   let ini = strpart(group, 0, i-1)
  193.   let mid = substitute(strpart(group, i,j-i-1), s:notslash.'\zs:', '\\|', 'g')
  194.   let fin = strpart(group, j)
  195.   " searchpair() requires that these patterns avoid \(\) groups.
  196.   let ini = substitute(ini, s:notslash . '\zs\\(', '\\%(', 'g')
  197.   let mid = substitute(mid, s:notslash . '\zs\\(', '\\%(', 'g')
  198.   let fin = substitute(fin, s:notslash . '\zs\\(', '\\%(', 'g')
  199.   " Set mid.  This is optimized for readability, not micro-efficiency!
  200.   if a:forward && matchline =~ prefix . fin . suffix
  201.     \ || !a:forward && matchline =~ prefix . ini . suffix
  202.     let mid = ""
  203.   endif
  204.   " Set flag.  This is optimized for readability, not micro-efficiency!
  205.   if a:forward && matchline =~ prefix . fin . suffix
  206.     \ || !a:forward && matchline !~ prefix . ini . suffix
  207.     let flag = "bW"
  208.   else
  209.     let flag = "W"
  210.   endif
  211.   " Set skip.
  212.   if exists("b:match_skip")
  213.     let skip = b:match_skip
  214.   elseif exists("b:match_comment") " backwards compatibility and testing!
  215.     let skip = "r:" . b:match_comment
  216.   else
  217.     let skip = 's:comment\|string'
  218.   endif
  219.   let skip = s:ParseSkip(skip)
  220.   if exists("b:match_debug")
  221.     let b:match_ini = ini
  222.     let b:match_tail = (strlen(mid) ? mid.'\|' : '') . fin
  223.   endif
  224.  
  225.   " Fifth step:  actually start moving the cursor and call searchpair().
  226.   " This minimizes screen jumps and avoids using a global mark.
  227.   let restore_cursor = line(".") . "G" . virtcol(".") . "|"
  228.   normal! H
  229.   let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor
  230.   execute restore_cursor
  231.   " Later, :execute restore_cursor to get to the original screen.
  232.   let restore_options = (&ic ? "" : "no") . "ignorecase"
  233.   if exists("b:match_ignorecase")
  234.     let &ignorecase = b:match_ignorecase
  235.   endif
  236.   " Instead of the next few lines, could I :execute "normal!".(curcol+1)."|" ?
  237.   normal! 0
  238.   if curcol
  239.     execute "normal!" . curcol . "l"
  240.   endif
  241.   if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on"))
  242.     let skip = "0"
  243.   else
  244.     execute "if " . skip . "| let skip = '0' | endif"
  245.   endif
  246.   let sp_return = searchpair(ini, mid, fin, flag, skip)
  247.   let final_position = line(".") . "normal!" . virtcol(".") . "|"
  248.   " Restore options, cursor position, and original screen.
  249.   execute "set " . restore_options
  250.   execute restore_cursor
  251.   normal!m'
  252.   if sp_return > 0
  253.     execute final_position
  254.     " Open folds, if appropriate.
  255.     if a:mode != "o" && &foldopen =~ "percent"
  256.       normal!zv
  257.     endif
  258.   endif
  259. endfun
  260.  
  261. " Example (simplified HTML patterns):  if
  262. "   a:groupBR    = '<\(\k\+\)>:</\1>'
  263. "   a:prefix    = '^.\{3}\('
  264. "   a:group    = '<\(\k\+\)>:</\(\k\+\)>'
  265. "   a:suffix    = '\).\{2}$'
  266. "   a:matchline    =  "123<tag>12" or "123</tag>12"
  267. " then extract "tag" from a:matchline and return "<tag>:</tag>" .
  268. fun! s:InsertRefs(groupBR, prefix, group, suffix, matchline)
  269.   if a:matchline !~ a:prefix .
  270.     \ substitute(a:group, s:notslash . '\zs:', '\\|', 'g') . a:suffix
  271.     return a:group
  272.   endif
  273.   let i = matchend(a:groupBR, s:notslash . ':')
  274.   let ini = strpart(a:groupBR, 0, i-1)
  275.   let tailBR = strpart(a:groupBR, i)
  276.   let word = s:Choose(a:group, a:matchline, ":", "", a:prefix, a:suffix,
  277.     \ a:groupBR)
  278.   let i = matchend(word, s:notslash . ":")
  279.   let wordBR = strpart(word, i)
  280.   let word = strpart(word, 0, i-1)
  281.   " Now, a:matchline =~ a:prefix . word . a:suffix
  282.   if wordBR != ini
  283.     let table = s:Resolve(ini, wordBR, "table")
  284.   else
  285.     " let table = "----------"
  286.     let table = ""
  287.     let d = 0
  288.     while d < 10
  289.       if tailBR =~ s:notslash . '\\' . d
  290.     " let table[d] = d
  291.     let table = table . d
  292.       else
  293.     let table = table . "-"
  294.       endif
  295.       let d = d + 1
  296.     endwhile
  297.   endif
  298.   let d = 9
  299.   while d
  300.     if table[d] != "-"
  301.       let backref = substitute(a:matchline, a:prefix.word.a:suffix,
  302.     \ '\'.table[d], "")
  303.     " Are there any other characters that should be escaped?
  304.       let backref = escape(backref, '*,:')
  305.       execute s:Ref(ini, d, "start", "len")
  306.       let ini = strpart(ini, 0, start) . backref . strpart(ini, start+len)
  307.       let tailBR = substitute(tailBR, s:notslash . '\zs\\' . d,
  308.     \ escape(backref, '\\'), 'g')
  309.     endif
  310.     let d = d-1
  311.   endwhile
  312.   if exists("b:match_debug")
  313.     if s:do_BR
  314.       let b:match_table = table
  315.       let b:match_word = word
  316.     else
  317.       let b:match_table = ""
  318.       let b:match_word = ""
  319.     endif
  320.   endif
  321.   return ini . ":" . tailBR
  322. endfun
  323.  
  324. " Input a comma-separated list of groups with backrefs, such as
  325. "   a:groups = '\(foo\):end\1,\(bar\):end\1'
  326. " and return a comma-separated list of groups with backrefs replaced:
  327. "   return '\(foo\):end\(foo\),\(bar\):end\(bar\)'
  328. fun! s:ParseWords(groups)
  329.   let groups = substitute(a:groups.",", s:notslash.'\zs[,:]*,[,:]*', ',', 'g')
  330.   let groups = substitute(groups, s:notslash . '\zs:\{2,}', ':', 'g')
  331.   let parsed = ""
  332.   while groups =~ '[^,:]'
  333.     let i = matchend(groups, s:notslash . ':')
  334.     let j = matchend(groups, s:notslash . ',')
  335.     let ini = strpart(groups, 0, i-1)
  336.     let tail = strpart(groups, i, j-i-1) . ":"
  337.     let groups = strpart(groups, j)
  338.     let parsed = parsed . ini
  339.     let i = matchend(tail, s:notslash . ':')
  340.     while i != -1
  341.       " In 'if:else:endif', ini='if' and word='else' and then word='endif'.
  342.       let word = strpart(tail, 0, i-1)
  343.       let tail = strpart(tail, i)
  344.       let i = matchend(tail, s:notslash . ':')
  345.       let parsed = parsed . ":" . s:Resolve(ini, word, "word")
  346.     endwhile " Now, tail has been used up.
  347.     let parsed = parsed . ","
  348.   endwhile " groups =~ '[^,:]'
  349.   return parsed
  350. endfun
  351.  
  352. " TODO I think this can be simplified and/or made more efficient.
  353. " TODO What should I do if a:start is out of range?
  354. " Return a regexp that matches all of a:string, such that
  355. " matchstr(a:string, regexp) represents the match for a:pat that starts
  356. " as close to a:start as possible, before being preferred to after, and
  357. " ends after a:start .
  358. " Usage:
  359. " let regexp = s:Wholematch(getline("."), 'foo\|bar', col(".")-1)
  360. " let i      = match(getline("."), regexp)
  361. " let j      = matchend(getline("."), regexp)
  362. " let match  = matchstr(getline("."), regexp)
  363. fun! s:Wholematch(string, pat, start)
  364.   if a:pat =~ '^\\%\=(.*\\)$'
  365.     let group = a:pat
  366.   else
  367.     let group = '\(' . a:pat . '\)'
  368.   endif
  369.   let prefix = (a:start ? '\(^.\{,' . a:start . '}\)\@<=' : '^')
  370.   let len = strlen(a:string)
  371.   let suffix = (a:start+1 < len ? '\(.\{,'.(len-a:start-1).'}$\)\@=' : '$')
  372.   if a:string !~ prefix . group . suffix
  373.     let prefix = ''
  374.   endif
  375.   return prefix . group . suffix
  376. endfun
  377.  
  378. " No extra arguments:  s:Ref(string, d) will
  379. " find the d'th occurrence of '\(' and return it, along with everything up
  380. " to and including the matching '\)'.
  381. " One argument:  s:Ref(string, d, "start") returns the index of the start
  382. " of the d'th '\(' and any other argument returns the length of the group.
  383. " Two arguments:  s:Ref(string, d, "foo", "bar") returns a string to be
  384. " executed, having the effect of
  385. "   :let foo = s:Ref(string, d, "start")
  386. "   :let bar = s:Ref(string, d, "len")
  387. fun! s:Ref(string, d, ...)
  388.   let len = strlen(a:string)
  389.   if a:d == 0
  390.     let start = 0
  391.   else
  392.     let cnt = a:d
  393.     let match = a:string
  394.     while cnt
  395.       let cnt = cnt - 1
  396.       let index = matchend(match, s:notslash . '\\(')
  397.       if index == -1
  398.     return ""
  399.       endif
  400.       let match = strpart(match, index)
  401.     endwhile
  402.     let start = len - strlen(match)
  403.     if a:0 == 1 && a:1 == "start"
  404.       return start - 2
  405.     endif
  406.     let cnt = 1
  407.     while cnt
  408.       let index = matchend(match, s:notslash . '\\(\|\\)') - 1
  409.       if index == -2
  410.     return ""
  411.       endif
  412.       " Increment if an open, decrement if a ')':
  413.       let cnt = cnt + (match[index]=="(" ? 1 : -1)  " ')'
  414.       " let cnt = stridx('0(', match[index]) + cnt
  415.       let match = strpart(match, index+1)
  416.     endwhile
  417.     let start = start - 2
  418.     let len = len - start - strlen(match)
  419.   endif
  420.   if a:0 == 1
  421.     return len
  422.   elseif a:0 == 2
  423.     return "let " . a:1 . "=" . start . "| let " . a:2 . "=" . len
  424.   else
  425.     return strpart(a:string, start, len)
  426.   endif
  427. endfun
  428.  
  429. " Count the number of disjoint copies of pattern in string.
  430. " If the pattern is a literal string and contains no '0' or '1' characters
  431. " then s:Count(string, pattern, '0', '1') should be faster than
  432. " s:Count(string, pattern).
  433. fun! s:Count(string, pattern, ...)
  434.   let pat = escape(a:pattern, '\\')
  435.   if a:0 > 1
  436.     let foo = substitute(a:string, '[^'.a:pattern.']', "a:1", "g")
  437.     let foo = substitute(a:string, pat, a:2, "g")
  438.     let foo = substitute(foo, '[^' . a:2 . ']', "", "g")
  439.     return strlen(foo)
  440.   endif
  441.   let result = 0
  442.   let foo = a:string
  443.   let index = matchend(foo, pat)
  444.   while index != -1
  445.     let result = result + 1
  446.     let foo = strpart(foo, index)
  447.     let index = matchend(foo, pat)
  448.   endwhile
  449.   return result
  450. endfun
  451.  
  452. " s:Resolve('\(a\)\(b\)', '\(c\)\2\1\1\2') should return table.word, where
  453. " word = '\(c\)\(b\)\(a\)\3\2' and table = '-32-------'.  That is, the first
  454. " '\1' in target is replaced by '\(a\)' in word, table[1] = 3, and this
  455. " indicates that all other instances of '\1' in target are to be replaced
  456. " by '\3'.  The hard part is dealing with nesting...
  457. " Note that ":" is an illegal character for source and target,
  458. " unless it is preceded by "\".
  459. fun! s:Resolve(source, target, output)
  460.   let word = a:target
  461.   let i = matchend(word, s:notslash . '\\\d') - 1
  462.   let table = "----------"
  463.   while i != -2 " There are back references to be replaced.
  464.     let d = word[i]
  465.     let backref = s:Ref(a:source, d)
  466.     " The idea is to replace '\d' with backref.  Before we do this,
  467.     " replace any \(\) groups in backref with :1, :2, ... if they
  468.     " correspond to the first, second, ... group already inserted
  469.     " into backref.  Later, replace :1 with \1 and so on.  The group
  470.     " number w+b within backref corresponds to the group number
  471.     " s within a:source.
  472.     " w = number of '\(' in word before the current one
  473.     let w = s:Count(
  474.     \ substitute(strpart(word, 0, i-1), '\\\\', '', 'g'), '\(', '1')
  475.     let b = 1 " number of the current '\(' in backref
  476.     let s = d " number of the current '\(' in a:source
  477.     while b <= s:Count(substitute(backref, '\\\\', '', 'g'), '\(', '1')
  478.     \ && s < 10
  479.       if table[s] == "-"
  480.     if w + b < 10
  481.       " let table[s] = w + b
  482.       let table = strpart(table, 0, s) . (w+b) . strpart(table, s+1)
  483.     endif
  484.     let b = b + 1
  485.     let s = s + 1
  486.       else
  487.     execute s:Ref(backref, b, "start", "len")
  488.     let ref = strpart(backref, start, len)
  489.     let backref = strpart(backref, 0, start) . ":". table[s]
  490.     \ . strpart(backref, start+len)
  491.     let s = s + s:Count(substitute(ref, '\\\\', '', 'g'), '\(', '1')
  492.       endif
  493.     endwhile
  494.     let word = strpart(word, 0, i-1) . backref . strpart(word, i+1)
  495.     let i = matchend(word, s:notslash . '\\\d') - 1
  496.   endwhile
  497.   let word = substitute(word, s:notslash . '\zs:', '\\', 'g')
  498.   if a:output == "table"
  499.     return table
  500.   elseif a:output == "word"
  501.     return word
  502.   else
  503.     return table . word
  504.   endif
  505. endfun
  506.  
  507. " Assume a:comma = ",".  Then the format for a:patterns and a:1 is
  508. "   a:patterns = "<pat1>,<pat2>,..."
  509. "   a:1 = "<alt1>,<alt2>,..."
  510. " If <patn> is the first pattern that matches a:string then return <patn>
  511. " if no optional arguments are given; return <patn>,<altn> if a:1 is given.
  512. fun! s:Choose(patterns, string, comma, branch, prefix, suffix, ...)
  513.   let tail = (a:patterns =~ a:comma."$" ? a:patterns : a:patterns . a:comma)
  514.   let i = matchend(tail, s:notslash . a:comma)
  515.   if a:0
  516.     let alttail = (a:1 =~ a:comma."$" ? a:1 : a:1 . a:comma)
  517.     let j = matchend(alttail, s:notslash . a:comma)
  518.   endif
  519.   let current = strpart(tail, 0, i-1)
  520.   if a:branch == ""
  521.     let currpat = current
  522.   else
  523.     let currpat = substitute(current, a:branch, '\\|', 'g')
  524.   endif
  525.   while a:string !~ a:prefix . currpat . a:suffix
  526.     let tail = strpart(tail, i)
  527.     let i = matchend(tail, s:notslash . a:comma)
  528.     if i == -1
  529.       return -1
  530.     endif
  531.     let current = strpart(tail, 0, i-1)
  532.     if a:branch == ""
  533.       let currpat = current
  534.     else
  535.       let currpat = substitute(current, a:branch, '\\|', 'g')
  536.     endif
  537.     if a:0
  538.       let alttail = strpart(alttail, j)
  539.       let j = matchend(alttail, s:notslash . a:comma)
  540.     endif
  541.   endwhile
  542.   if a:0
  543.     let current = current . a:comma . strpart(alttail, 0, j-1)
  544.   endif
  545.   return current
  546. endfun
  547.  
  548. " Call this function to turn on debugging information.  Every time the main
  549. " script is run, buffer variables will be saved.  These can be used directly
  550. " or viewed using the menu items below.
  551. if !exists(":MatchDebug")
  552.   command! -nargs=0 MatchDebug call s:Match_debug()
  553. endif
  554.  
  555. fun! s:Match_debug()
  556.   let b:match_debug = 1    " Save debugging information.
  557.   " pat = all of b:match_words with backrefs parsed
  558.   amenu &Matchit.&pat    :echo b:match_pat<CR>
  559.   " match = bit of text that is recognized as a match
  560.   amenu &Matchit.&match    :echo b:match_match<CR>
  561.   " curcol = cursor column of the start of the matching text
  562.   amenu &Matchit.&curcol    :echo b:match_col<CR>
  563.   " wholeBR = matching group, original version
  564.   amenu &Matchit.wh&oleBR    :echo b:match_wholeBR<CR>
  565.   " iniBR = 'if' piece, original version
  566.   amenu &Matchit.ini&BR    :echo b:match_iniBR<CR>
  567.   " ini = 'if' piece, with all backrefs resolved from match
  568.   amenu &Matchit.&ini    :echo b:match_ini<CR>
  569.   " tail = 'else\|endif' piece, with all backrefs resolved from match
  570.   amenu &Matchit.&tail    :echo b:match_tail<CR>
  571.   " fin = 'endif' piece, with all backrefs resolved from match
  572.   amenu &Matchit.&word    :echo b:match_word<CR>
  573.   " '\'.d in ini refers to the same thing as '\'.table[d] in word.
  574.   amenu &Matchit.t&able    :echo '0:' . b:match_table . ':9'<CR>
  575. endfun
  576.  
  577. " Jump to the nearest unmatched "(" or "if" or "<tag>" if a:spflag == "bW"
  578. " or the nearest unmatched "</tag>" or "endif" or ")" if a:spflag == "W".
  579. " Return a "mark" for the original position, so that
  580. "   let m = MultiMatch("bW", "n") ... execute m
  581. " will return to the original position.  If there is a problem, do not
  582. " move the cursor and return "", unless a count is given, in which case
  583. " go up or down as many levels as possible and again return "".
  584. " TODO This relies on the same patterns as % matching.  It might be a good
  585. " idea to give it its own matching patterns.
  586. fun! s:MultiMatch(spflag, mode)
  587.   if !exists("b:match_words") || b:match_words == ""
  588.     return ""
  589.   end
  590.  
  591.   " First step:  if not already done, set the script variables
  592.   "   s:do_BR    flag for whether there are backrefs
  593.   "   s:pat    parsed version of b:match_words
  594.   "   s:all    regexp based on s:pat and the default groups
  595.   " This part is copied and slightly modified from s:Match_wrapper().
  596.   let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") .
  597.     \ '\/\*:\*\/,#if\%(def\)\=:$else\>:#elif\>:#endif\>'
  598.   if (b:match_words != s:last_words) || (&mps != s:last_mps) ||
  599.     \ exists("b:match_debug")
  600.     let s:last_words = b:match_words
  601.     let s:last_mps = &mps
  602.     if b:match_words !~ s:notslash . '\\\d'
  603.       let s:do_BR = 0
  604.       let s:pat = b:match_words
  605.     else
  606.       let s:do_BR = 1
  607.       let s:pat = s:ParseWords(b:match_words)
  608.     endif
  609.     let s:all = '\%(' . substitute(s:pat . (strlen(s:pat)?",":"") . default,
  610.       \    '[,:]\+','\\|','g') . '\)'
  611.     if exists("b:match_debug")
  612.       let b:match_pat = s:pat
  613.     endif
  614.   endif
  615.  
  616.   " Second step:  figure out the patterns for searchpair()
  617.   " and save the screen, cursor position, and 'ignorecase'.
  618.   " - TODO:  A lot of this is copied from s:Match_wrapper().
  619.   " - maybe even more functionality should be split off
  620.   " - into separate functions!
  621.   let open =  substitute(s:pat . default, ':[^,]*,', '\\),\\(', 'g')
  622.   let open =  '\(' . substitute(open, ':[^,]*$', '\\)', '')
  623.   let close = substitute(s:pat . default, ',[^,]*:', '\\),\\(', 'g')
  624.   let close = substitute(close, '[^,]*:', '\\(', '') . '\)'
  625.   if exists("b:match_skip")
  626.     let skip = b:match_skip
  627.   elseif exists("b:match_comment") " backwards compatibility and testing!
  628.     let skip = "r:" . b:match_comment
  629.   else
  630.     let skip = 's:comment\|string'
  631.   endif
  632.   let skip = s:ParseSkip(skip)
  633.   let restore_cursor = line(".") . "G" . virtcol(".") . "|"
  634.   normal! H
  635.   let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor
  636.   execute restore_cursor
  637.   let restore_options = (&ic ? "" : "no") . "ignorecase"
  638.   if exists("b:match_ignorecase")
  639.     let &ignorecase = b:match_ignorecase
  640.   endif
  641.  
  642.   " Third step: call searchpair().
  643.   " Replace '\('--but not '\\('--with '\%(' and ',' with '\|'.
  644.   let openpat =  substitute(open, '\(\\\@<!\(\\\\\)*\)\@<=\\(', '\\%(', 'g')
  645.   let openpat = substitute(openpat, ',', '\\|', 'g')
  646.   let closepat = substitute(close, '\(\\\@<!\(\\\\\)*\)\@<=\\(', '\\%(', 'g')
  647.   let closepat = substitute(closepat, ',', '\\|', 'g')
  648.   if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on"))
  649.     let skip = '0'
  650.   else
  651.     execute "if " . skip . "| let skip = '0' | endif"
  652.   endif
  653.   mark '
  654.   let level = v:count1
  655.   while level
  656.     if searchpair(openpat, '', closepat, a:spflag, skip) < 1
  657.       execute "set " . restore_options
  658.       return ""
  659.     endif
  660.     let level = level - 1
  661.   endwhile
  662.  
  663.   " Open folds, if appropriate.
  664.   if a:mode != "o" && &foldopen =~ "percent"
  665.     normal!zv
  666.   endif
  667.   " Restore options and return a string to restore the original position.
  668.   execute "set " . restore_options
  669.   return restore_cursor
  670. endfun
  671.  
  672. " Search backwards for "if" or "while" or "<tag>" or ...
  673. " and return "endif" or "endwhile" or "</tag>" or ... .
  674. " For now, this uses b:match_words and the same script variables
  675. " as s:Match_wrapper() .  Later, it may get its own patterns,
  676. " either from a buffer variable or passed as arguments.
  677. " fun! s:Autocomplete()
  678. "   echo "autocomplete not yet implemented :-("
  679. "   if !exists("b:match_words") || b:match_words == ""
  680. "     return ""
  681. "   end
  682. "   let startpos = s:MultiMatch("bW")
  683. "
  684. "   if startpos == ""
  685. "     return ""
  686. "   endif
  687. "   " - TODO:  figure out whether 'if' or '<tag>' matched, and construct
  688. "   " - the appropriate closing.
  689. "   let matchline = getline(".")
  690. "   let curcol = col(".") - 1
  691. "   " - TODO:  Change the s:all argument if there is a new set of match pats.
  692. "   let regexp = s:Wholematch(matchline, s:all, curcol)
  693. "   let suf = strlen(matchline) - matchend(matchline, regexp)
  694. "   let prefix = (curcol ? '^.\{'  . curcol . '}\%(' : '^\%(')
  695. "   let suffix = (suf ? '\).\{' . suf . '}$'  : '\)$')
  696. "   " Reconstruct the version with unresolved backrefs.
  697. "   let patBR = substitute(b:match_words.',', '[,:]*,[,:]*', ',', 'g')
  698. "   let patBR = substitute(patBR, ':\{2,}', ':', "g")
  699. "   " Now, set group and groupBR to the matching group: 'if:endif' or
  700. "   " 'while:endwhile' or whatever.
  701. "   let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR)
  702. "   let i = matchend(group, s:notslash . ",")
  703. "   let groupBR = strpart(group, i)
  704. "   let group = strpart(group, 0, i-1)
  705. "   " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix
  706. "   if s:do_BR
  707. "     let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline)
  708. "   endif
  709. " " let g:group = group
  710. "
  711. "   " - TODO:  Construct the closing from group.
  712. "   let fake = "end" . expand("<cword>")
  713. "   execute startpos
  714. "   return fake
  715. " endfun
  716.  
  717. " Close all open structures.  "Get the heck out of here!"
  718. " fun! s:Gthhoh()
  719. "   let close = s:Autocomplete()
  720. "   while strlen(close)
  721. "     put=close
  722. "     let close = s:Autocomplete()
  723. "   endwhile
  724. " endfun
  725.  
  726. " Parse special strings as typical skip arguments for searchpair():
  727. "   s:foo becomes (current syntax item) =~ foo
  728. "   S:foo becomes (current syntax item) !~ foo
  729. "   r:foo becomes (line before cursor) =~ foo
  730. "   R:foo becomes (line before cursor) !~ foo
  731. fun! s:ParseSkip(str)
  732.   let skip = a:str
  733.   if skip[1] == ":"
  734.     if skip[0] == "s"
  735.       let skip = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '" .
  736.     \ strpart(skip,2) . "'"
  737.     elseif skip[0] == "S"
  738.       let skip = "synIDattr(synID(line('.'),col('.'),1),'name') !~? '" .
  739.     \ strpart(skip,2) . "'"
  740.     elseif skip[0] == "r"
  741.       let skip = "strpart(getline('.'),0,col('.'))=~'" . strpart(skip,2). "'"
  742.     elseif skip[0] == "R"
  743.       let skip = "strpart(getline('.'),0,col('.'))!~'" . strpart(skip,2). "'"
  744.     endif
  745.   endif
  746.   return skip
  747. endfun
  748.  
  749. aug Matchit
  750.   let s:notend = '\%(\<end\s\+\)\@<!'
  751.   au!
  752.   " ASP:  Active Server Pages (with Visual Basic Script)
  753.   " thanks to Gontran BAERTS
  754.   au FileType aspvbs if !exists("b:match_words") |
  755.     \ do Matchit FileType html |
  756.   \ let b:match_words =
  757.   \ s:notend . '\<If\>:^\s\+\<Else\>:\<ElseIf\>:\<end\s\+\<if\>,' .
  758.   \ s:notend . '\<Select\s\+\<Case\>:\<Case\>:\<Case\s\+\<Else\>:' .
  759.   \    '\<End\s\+\<Select\>,' .
  760.   \ '^\s*\<Sub\>:\<End\s\+\<Sub\>,' .
  761.   \ '^\s*\<Function\>:\<End\s\+\<Function\>,' .
  762.   \ '\<Class\>:\<End\s\+\<Class\>,' .
  763.   \ '^\s*\<Do\>:\<Loop\>,' .
  764.   \ '^\s*\<For\>:\<Next\>,' .
  765.   \ '\<While\>:\<Wend\>,' .
  766.   \ b:match_words
  767.   \ | endif
  768.   " Csh:  thanks to Johannes Zellner
  769.   " - Both foreach and end must appear alone on separate lines.
  770.   " - The words else and endif must appear at the beginning of input lines;
  771.   "   the if must appear alone on its input line or after an else.
  772.   " - Each case label and the default label must appear at the start of a
  773.   "   line.
  774.   " - while and end must appear alone on their input lines.
  775.   au FileType csh,tcsh  if !exists("b:match_words") |
  776.     \ let b:match_words =
  777.       \ '^\s*\<if\>.*(.*).*\<then\>:'.
  778.       \   '^\s*\<else\>\s\+\<if\>.*(.*).*\<then\>:^\s*\<else\>:'.
  779.       \   '^\s*\<endif\>,'.
  780.       \ '\%(^\s*\<foreach\>\s\+\S\+\|^s*\<while\>\).*(.*):'.
  781.       \   '\<break\>:\<continue\>:^\s*\<end\>,'.
  782.       \ '^\s*\<switch\>.*(.*):^\s*\<case\>\s\+:^\s*\<default\>:^\s*\<endsw\>'
  783.       \ | endif
  784.   " DTD:  thanks to Johannes Zellner
  785.   " - match <!--, --> style comments.
  786.   " - match <! with >
  787.   " - TODO:  why does '--:--,'. not work ?
  788.   au! FileType dtd if !exists("b:match_words") |
  789.     \ let b:match_words =
  790.     \ '<!--:-->,'.
  791.     \ '<!:>'
  792.     \ | endif
  793.   " Entity:  see XML.
  794.   " Essbase:
  795.   au BufNewFile,BufRead *.csc if !exists("b:match_words") |
  796.     \ let b:match_words=
  797.   \ '\<fix\>:\<endfix\>,' .
  798.   \ '\<if\>:\<else\%(if\)\=\>:\<endif\>,' .
  799.   \ '\<!loopondimensions\>\|\<!looponselected\>:\<!endloop\>'
  800.   \ | endif
  801.   " HTML:  thanks to Johannes Zellner.
  802.     au FileType html,jsp if !exists("b:match_words") |
  803.       \    let b:match_ignorecase = 1 |
  804.       \    let b:match_skip = 's:Comment' |
  805.       \ let b:match_words = '<:>,' .
  806.       \ '<\@<=[ou]l[^>]*\%(>\|$\):<\@<=li>:<\@<=/[ou]l>,' .
  807.       \ '<\@<=\([^/][^ \t>]*\)[^>]*\%(>\|$\):<\@<=/\1>'
  808.       \ | endif
  809.   " Pascal:
  810.   au FileType pascal if !exists("b:match_words") |
  811.     \ let b:match_words='\<begin\>:\<end\>'
  812.     \ | endif
  813.   " SGML:  see XML
  814.   " Shell:  thanks to Johannes Zellner
  815.   let s:sol = '\%(;\s*\|^\s*\)\@<='  " start of line
  816.   au FileType sh,config if !exists("b:match_words") |
  817.     \ let b:match_words =
  818.       \ s:sol.'if\>:' . s:sol.'elif\>:' . s:sol.'else\>:' . s:sol. 'fi\>,' .
  819.       \ s:sol.'\%(for\|while\)\>:' . s:sol. 'done\>,' .
  820.       \ s:sol.'case\>:' . s:sol. 'esac\>'
  821.       \ | endif
  822.   " RPM Spec:  thanks to Max Ischenko
  823.   au FileType spec if !exists("b:match_words") |
  824.     \ let b:match_ignorecase = 0 | let b:match_words =
  825.     \ '^Name:^%description:^%clean:^%setup:^%build:^%install:^%files:' .
  826.     \ '^%package:^%preun:^%postun:^%changelog'
  827.     \ | endif
  828.   " Tcsh:  see Csh
  829.   " Verilog:  thanks to Mark Collett
  830.   au FileType verilog if !exists("b:match_words") |
  831.     \ let b:match_ignorecase = 0
  832.       \ | let b:match_words =
  833.       \ '\<begin\>:\<end\>,'.
  834.       \ '\<case\>\|\<casex\>\|\<casez\>:\<endcase\>,'.
  835.       \ '\<module\>:\<endmodule\>,'.
  836.       \ '\<function\>:\<endfunction\>,'.
  837.       \ '`ifdef\>:`else\>:`endif\>'
  838.       \ | endif
  839.   " XML:  thanks to Johannes Zellner and Akbar Ibrahim
  840.   " - case sensitive
  841.   " - don't match empty tags <fred/>
  842.   " - match <!--, --> style comments (but not --, --)
  843.   " - match <!, > inlined dtd's. This is not perfect, as it
  844.   "   gets confused for example by
  845.   "       <!ENTITY gt ">">
  846.   au! FileType xml,sgml,entity if !exists("b:match_words") |
  847.     \ let b:match_ignorecase=0 | let b:match_words =
  848.     \ '<:>,' .
  849.     \ '<\@<=!\[CDATA\[:]]>,'.
  850.     \ '<\@<=!--:-->,'.
  851.     \ '<\@<=?\k\+:?>,'.
  852.     \ '<\@<=\([^ \t>/]\+\)\%(\s\+[^>]*\%([^/]>\|$\)\|>\|$\):<\@<=/\1>,'.
  853.     \ '<\@<=\%([^ \t>/]\+\)\%(\s\+[^/>]*\|$\):/>'
  854.     \ | endif
  855.  
  856. aug END
  857.  
  858. let &cpo = s:save_cpo
  859.  
  860. " vim:sts=2:sw=2:
  861.